#pragma once
#include "base/function.hpp"

namespace myrpc::base{
namespace detail{

template<typename... Datas>
struct Tuple{};

template<typename T,typename... Datas>
struct Tuple<T, Datas...> : Tuple<Datas...>{
  using Type = T;
  using Type_ptr = T*;
  using Base = Tuple<Datas...>;
};

template<typename T_>
struct Traits_Ret_Arg : public Traits_Ret_Arg<decltype(&T_::operator())>{
  using T = T_;
};

template<typename Lambda,typename Ret_,typename... Args_>
struct Traits_Ret_Arg<Ret_(Lambda::*)(Args_...) const>{
  using Ret_Args = Tuple<Ret_,Args_...>;
};

template<typename Ret_,typename... Args_>
struct Traits_Ret_Arg<Ret_(*)(Args_...)>{
  using T = Ret_(*)(Args_...);
  using Ret_Args = Tuple<Ret_,Args_...>;
};

template<typename Ret_,typename... Args_>
struct Traits_Ret_Arg<Ret_(Args_...)>{
  using T = Ret_(*)(Args_...);
  using Ret_Args = Tuple<Ret_,Args_...>;
};

} // namespace detail

template <size_t Buffer_size = 64>
class Rpccall{
 public:
  using Con = net_tools::net::Connect;
  using final_call = void(*)(Rpccall*,char*);
  using final_call_ret = void(*)(Rpccall*,char*,Con*);
  Rpccall()
    :func_body_(nullptr),
     cb_(nullptr),
     final_call_func_(nullptr),
     final_call_ret_func_(nullptr)
  {
  };
  ~Rpccall(){};

  template<typename Ret,typename T,typename... Args>
  void Init(detail::Tuple<Ret,Args...>,T func_body,rpc_callback cb){
    using Type = std::decay_t<T>;
    cb_ = std::move(cb);
    static_assert(sizeof(Type) <= Buffer_size, "function_body too big");
    final_call_func_ = [](Rpccall<>* this_,char* buf){
      Rpccall::Call<Ret,Args...>(*reinterpret_cast<T*>(&this_->buffer_),buf);
    };
    if constexpr(std::is_void<Ret>::value)
      final_call_ret_func_ = [](Rpccall<>* this_,char* buf,Con* con){
        Rpccall::Call<void,Args...>(*reinterpret_cast<T*>(&this_->buffer_),buf);
        this_->cb_(con,nullptr,0);
      };
    else
      final_call_ret_func_ = [](Rpccall<>* this_,char* buf,Con* con){
        Ret ret = Rpccall::Call<Ret,Args...>(*reinterpret_cast<T*>(&this_->buffer_),buf);
        this_->cb_(con,reinterpret_cast<void*>(&ret),sizeof(Ret));
      };

    auto alloc = [](void* buffer_ptr,void* object_ptr){
      Type* body = static_cast<Type*>(object_ptr);
      new(buffer_ptr) Type(std::move(*body));
    };
    alloc(&buffer_,&func_body);
  };

  operator bool(){
    return final_call_func_;
  };

  void Final_call(char* buf){
    final_call_func_(this,buf);
  };
  void Final_call_ret(char* buf,Con* con){
    final_call_ret_func_(this,buf,con);
  };

 private:
  union{
		typename std::aligned_storage<Buffer_size,sizeof(size_t)>::type buffer_;
		void* func_body_;
  };
  rpc_callback cb_;
  final_call final_call_func_;
  final_call_ret final_call_ret_func_;

 public:
  template <typename Ret,typename... Args,typename T>
  static Ret Call(T&& func_body,char* buf){
    return Call<Ret>(std::forward<T>(func_body),detail::Tuple<Args...>(),buf);
  };

  template <typename... Datas>
  static unsigned int Make(unsigned int type,char* buf,bool ret,Datas&&... datas){
    unsigned int* temp = reinterpret_cast<unsigned int*>(buf);
    *temp = type;
    buf += sizeof(unsigned int);
    unsigned int* size = reinterpret_cast<unsigned int*>(buf);
    buf += sizeof(unsigned int);
    *buf = ret;
    buf += sizeof(bool);
    *size = (Make(buf,std::forward<Datas>(datas)) + ...);
    return 9 + *size;
  };

 private:
  template<typename Ret,typename Func,typename Tuple,typename ...Datas>
  static Ret Call(Func&& func,Tuple,char* buf,Datas&&... args){
    typename Tuple::Type data = *reinterpret_cast<typename Tuple::Type_ptr>(buf);
    buf += sizeof(typename Tuple::Type);
    return Call<Ret>(std::forward<Func>(func),typename Tuple::Base(),buf,std::forward<Datas>(args)...,std::move(data));
  };
  template<typename Ret,typename Func,typename... Datas>
  static Ret Call(Func&& func,detail::Tuple<>,char* buf,Datas&&... args){
    return func(args...);
  };

  template<typename Data>
  static unsigned int Make(char*& buf,Data&& data){
    typename std::decay_t<Data>& temp = *reinterpret_cast<std::decay_t<Data>*>(buf);
    temp = data;
    buf += sizeof(std::decay_t<Data>);
    return sizeof(std::decay_t<Data>);
  };

  constexpr static unsigned int Make(char* buf){
    return 0;
  };

};

} // namespace myrpc::base
